package cn.iocoder.yudao.module.pay.wallet.service;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.business.basic.dao.repository.SerialNoRedisRepository;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.api.infrastructure.social.SocialClientApi;
import cn.iocoder.yudao.module.api.infrastructure.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import cn.iocoder.yudao.module.api.pay.order.dto.PayOrderCreateDTO;
import cn.iocoder.yudao.module.api.pay.order.eums.UnifiedOrderStatusEnum;
import cn.iocoder.yudao.module.api.pay.refund.dto.PayRefundCreateDTO;
import cn.iocoder.yudao.module.api.pay.refund.enums.UnifiedRefundStatusEnum;
import cn.iocoder.yudao.module.api.pay.wallet.enums.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.order.dal.dataobject.PayOrderDO;
import cn.iocoder.yudao.module.pay.order.service.PayOrderService;
import cn.iocoder.yudao.module.pay.refund.dal.dataobject.PayRefundDO;
import cn.iocoder.yudao.module.pay.refund.service.PayRefundService;
import cn.iocoder.yudao.module.pay.wallet.dal.dataobject.PayWalletDO;
import cn.iocoder.yudao.module.pay.wallet.dal.dataobject.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.wallet.dal.dataobject.PayWalletRechargePackageDO;
import cn.iocoder.yudao.module.pay.wallet.dal.mysql.PayWalletRechargeMapper;
import cn.iocoder.yudao.module.pay.wallet.mq.WalletProducer;
import cn.iocoder.yudao.module.pay.wallet.vo.AppPayWalletRechargeCreateReqVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Objects;

import static cn.hutool.core.util.ObjectUtil.notEqual;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.common.util.number.MoneyUtils.fenToYuanStr;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
import static cn.iocoder.yudao.module.api.pay.ConfigConstants.PAY_RECHARGE_NO_PREFIX;
import static cn.iocoder.yudao.module.api.pay.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.api.pay.MessageTemplateConstants.WXA_WALLET_RECHARGER_PAID;

/**
 * 钱包充值 Service 实现类
 *
 * @author jason
 */
@Service
@Slf4j
public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {

    private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值";

    @Resource
    private PayWalletRechargeMapper walletRechargeMapper;

    @Resource
    private PayWalletService payWalletService;
    @Resource
    private PayOrderService payOrderService;
    @Resource
    private PayRefundService payRefundService;
    @Resource
    private PayWalletRechargePackageService payWalletRechargePackageService;

    @Resource
    public SocialClientApi socialClientApi;
    @Resource
    public SerialNoRedisRepository serialNoRedisRepository;
    @Resource
    public WalletProducer walletProducer;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, AppPayWalletRechargeCreateReqVO reqVO) {
        // 1.1 计算充值金额
        int payPrice;
        int bonusPrice;
        if (Objects.nonNull(reqVO.getPackageId())) {
            PayWalletRechargePackageDO rechargePackage = payWalletRechargePackageService.validWalletRechargePackage(reqVO.getPackageId());
            payPrice = rechargePackage.getPayPrice();
            bonusPrice = rechargePackage.getBonusPrice();
        } else {
            payPrice = reqVO.getPayPrice();
            bonusPrice = 0;
        }
        // 1.2 插入充值记录
        PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
        PayWalletRechargeDO recharge = new PayWalletRechargeDO();
        recharge.setTradeNo(serialNoRedisRepository.generateNo(PAY_RECHARGE_NO_PREFIX));
        recharge.setWalletId(wallet.getId());
        recharge.setPayed(false);
        recharge.setRefunded(false);
        recharge.setPackageId(reqVO.getPackageId());
        recharge.setPayPrice(payPrice);
        recharge.setBonusPrice(bonusPrice);
        recharge.setTotalPrice(payPrice + bonusPrice);
        walletRechargeMapper.insert(recharge);
        // 2.1 创建支付单
        PayOrderCreateDTO createDTO = new PayOrderCreateDTO();
        createDTO.setTradeNo(recharge.getTradeNo());
        createDTO.setSubject(WALLET_RECHARGE_ORDER_SUBJECT);
        createDTO.setBody("");
        createDTO.setPrice(recharge.getPayPrice());
        payOrderService.createOrder(createDTO);
        return recharge;
    }

    @Override
    public PageResult<PayWalletRechargeDO> getWalletRechargePackagePage(Long userId, Integer userType,
                                                                        PageParam pageReqVO, Boolean payed) {
        PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
        return walletRechargeMapper.selectPage(pageReqVO, wallet.getId(), payed);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateWalletRechargePaid(Long id, String tradeNo) {
        // 1.1 校验钱包充值是否存在
        PayWalletRechargeDO recharge = walletRechargeMapper.selectById(id);
        if (recharge == null) {
            log.error("[updateWalletRechargePaid][recharge({}) payOrder({}) 不存在充值订单，请进行处理！]", id, tradeNo);
            throw exception(WALLET_RECHARGE_NOT_FOUND);
        }
        // 1.2 校验钱包充值是否可以支付
        if (recharge.getPayed()) {
            // 特殊：如果订单已支付，且支付单号相同，直接返回，说明重复回调
            if (ObjectUtil.equals(recharge.getTradeNo(), tradeNo)) {
                log.warn("[updateWalletRechargePaid][recharge({}) 已支付，且支付单号相同({})，直接返回]", recharge, tradeNo);
                return;
            }
            // 异常：支付单号不同，说明支付单号错误
            log.error("[updateWalletRechargePaid][recharge({}) 已支付，但是支付单号不同({})，请进行处理！]", recharge, tradeNo);
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
        }

        // 2. 校验付款单据的合法性
        PayOrderDO payOrderDO = validatePayOrderPaid(recharge, tradeNo);

        // 3. 更新钱包充值的支付状态
        PayWalletRechargeDO payWalletRechargeDO = new PayWalletRechargeDO();
        payWalletRechargeDO.setId(id);
        payWalletRechargeDO.setPayed(true);
        payWalletRechargeDO.setPayTime(LocalDateTime.now());
        payWalletRechargeDO.setPayChannelCode(payOrderDO.getChannelCode());
        int updateCount = walletRechargeMapper.updateByIdAndPaid(id, false, payWalletRechargeDO);
        if (updateCount == 0) {
            throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
        }

        // 4. 更新钱包余额
        // TODO @jason：这样的话，未来提现会不会把充值的，也提现走哈。类似先充 100，送 110；然后提现 110；
        // TODO 需要钱包中加个可提现余额
        payWalletService.addWalletBalance(recharge.getWalletId(), String.valueOf(id),
                PayWalletBizTypeEnum.RECHARGE, recharge.getTotalPrice());

        // 5. 发送订阅消息
        getSelf().sendWalletRechargerPaidMessage(payOrderDO, recharge);
    }

    @Async
    public void sendWalletRechargerPaidMessage(PayOrderDO payOrder, PayWalletRechargeDO walletRecharge) {
        walletProducer.sendWalletRechargerPaidMessage(payOrder.getTradeNo(), payOrder.getNo());
        // 1. 获得用户钱包信息
        PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId());
        // 2. 构建并发送模版消息 TODO 后续改为事件监听模式
        socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO()
                .setUserId(wallet.getUserId()).setUserType(wallet.getUserType())
                .setTemplateTitle(WXA_WALLET_RECHARGER_PAID)
                .setPage("pages/user/wallet/money") // 钱包详情界面
                .addMessage("character_string1", payOrder.getNo()) // 支付单号
                .addMessage("amount2", fenToYuanStr(NumberUtil.nullToZero(walletRecharge.getTotalPrice()))) // 充值金额
                .addMessage("time3", LocalDateTimeUtil.formatNormal(walletRecharge.getCreateTime())) // 充值时间
                .addMessage("phrase4", "充值成功")); // 充值状态
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refundWalletRecharge(Long id) {
        // 1.1 获取钱包充值记录
        PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
        if (walletRecharge == null) {
            log.error("[refundWalletRecharge][钱包充值记录不存在，钱包充值记录 id({})]", id);
            throw exception(WALLET_RECHARGE_NOT_FOUND);
        }
        // 1.2 校验钱包充值是否可以发起退款
        PayWalletDO wallet = validateWalletRechargeCanRefund(walletRecharge);
        // 2. 冻结退款的余额，暂时只处理赠送的余额也全部退回
        payWalletService.freezePrice(wallet.getId(), walletRecharge.getTotalPrice());
        // 3. 创建退款单
        PayRefundCreateDTO createDTO = new PayRefundCreateDTO();
        createDTO.setTradeNo(walletRecharge.getTradeNo());
        createDTO.setPayNo(walletRecharge.getPayNo());
        createDTO.setReason("想退钱");
        createDTO.setRefundPrice(walletRecharge.getPayPrice());
        createDTO.setUserIp(getClientIP());
        createDTO.setUserUa(getUserAgent());
        PayRefundDO refund = payRefundService.createRefund(createDTO);
        // 4. 更新充值记录退款单号
        PayWalletRechargeDO payWalletRechargeDO = new PayWalletRechargeDO();
        payWalletRechargeDO.setId(walletRecharge.getId());
        payWalletRechargeDO.setRefundNo(refund.getNo());
        payWalletRechargeDO.setRefunded(false);
        walletRechargeMapper.updateById(payWalletRechargeDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateWalletRechargeRefunded(Long id, String refundNo) {
        // 1.1 获取钱包充值记录
        PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
        if (walletRecharge == null) {
            log.error("[updateWalletRechargePaid][钱包充值记录不存在，钱包充值记录 id({})]", id);
            throw exception(WALLET_RECHARGE_NOT_FOUND);
        }
        // 1.2 校验钱包充值是否可以更新已退款
        PayRefundDO payRefund = validateWalletRechargeCanRefunded(walletRecharge, refundNo);

        PayWalletRechargeDO updateObj = new PayWalletRechargeDO();
        updateObj.setId(id);
        // 退款成功
        if (UnifiedRefundStatusEnum.isSuccess(payRefund.getStatus())) {
            // 2.1 更新钱包余额
            payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), String.valueOf(id),
                    PayWalletBizTypeEnum.RECHARGE_REFUND, walletRecharge.getTotalPrice());
            updateObj.setRefunded(true);
            updateObj.setRefundTime(payRefund.getSuccessTime());
            updateObj.setRefundTotalPrice(walletRecharge.getTotalPrice());
            updateObj.setRefundPayPrice(walletRecharge.getPayPrice());
            updateObj.setRefundBonusPrice(walletRecharge.getBonusPrice());
            walletRechargeMapper.updateByIdAndRefunded(id, updateObj);
            return;
        }
        // 退款失败
        if (UnifiedRefundStatusEnum.isFailure(payRefund.getStatus())) {
            // 2.2 解冻余额
            payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice());
            updateObj.setRefunded(false);
            walletRechargeMapper.updateByIdAndRefunded(id, updateObj);
        }
    }

    private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, String refundNo) {
        // 1. 校验退款订单匹配
        if (notEqual(walletRecharge.getRefundNo(), refundNo)) {
            log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({})，请进行处理！钱包充值的数据是：{}]",
                    walletRecharge.getId(), refundNo, toJsonString(walletRecharge));
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }

        // 2.1 校验退款订单
        PayRefundDO payRefund = payRefundService.getRefundByNo(refundNo);
        if (payRefund == null) {
            log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", refundNo);
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
        }
        // 2.2 校验退款金额一致
        if (notEqual(payRefund.getRefundPrice(), walletRecharge.getPayPrice())) {
            log.error("[validateWalletRechargeCanRefunded][钱包({}) payRefund({}) 退款金额不匹配，请进行处理！钱包数据是：{}，payRefund 数据是：{}]",
                    walletRecharge.getId(), refundNo, toJsonString(walletRecharge), toJsonString(payRefund));
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
        }
        // 2.3 校验退款订单商户订单是否匹配
        if (notEqual(payRefund.getTradeNo(), walletRecharge.getTradeNo())) {
            log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({})，请进行处理！payRefund 数据是：{}]",
                    walletRecharge.getId(), refundNo, toJsonString(payRefund));
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }
        return payRefund;
    }

    private PayWalletDO validateWalletRechargeCanRefund(PayWalletRechargeDO walletRecharge) {
        // 校验充值订单是否支付
        if (!walletRecharge.getPayed()) {
            throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID);
        }
        // 校验充值订单是否已退款
        if (walletRecharge.getRefundNo() != null) {
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED);
        }
        // 校验钱包余额是否足够
        PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId());
        Assert.notNull(wallet, "用户钱包({}) 不存在", wallet.getId());
        if (wallet.getBalance() < walletRecharge.getTotalPrice()) {
            throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH);
        }
        // TODO @芋艿：需要考虑下，赠送的金额，会不会导致提现超过；
        return wallet;
    }

    /**
     * 校验付款单据的合法性
     *
     * @param recharge 充值订单
     * @param tradeNo  交易单号
     * @return 付款单据
     */
    private PayOrderDO validatePayOrderPaid(PayWalletRechargeDO recharge, String tradeNo) {
        // 1. 校验支付单是否存在
        PayOrderDO payOrder = payOrderService.getOrderByNo(tradeNo);
        if (payOrder == null) {
            log.error("[validatePayOrderPaid][充值订单({}) tradeNo({}) 不存在，请进行处理！]",
                    recharge.getTradeNo(), tradeNo);
            throw exception(PAY_ORDER_NOT_FOUND);
        }

        // 2.1 校验支付单已支付
        if (!UnifiedOrderStatusEnum.isSuccess(payOrder.getStatus())) {
            log.error("[validatePayOrderPaid][充值订单({}) tradeNo({}) 未支付，请进行处理！payOrder 数据是：{}]",
                    recharge.getTradeNo(), tradeNo, toJsonString(payOrder));
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS);
        }
        // 2.2 校验支付金额一致
        if (notEqual(payOrder.getPrice(), recharge.getPayPrice())) {
            log.error("[validatePayOrderPaid][充值订单({}) tradeNo({}) 支付金额不匹配，请进行处理！钱包 数据是：{}，payOrder 数据是：{}]",
                    recharge.getTradeNo(), tradeNo, toJsonString(recharge), toJsonString(payOrder));
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH);
        }
        // 2.3 校验付款单据的商户订单匹配
        if (notEqual(payOrder.getTradeNo(), recharge.getTradeNo())) {
            log.error("[validatePayOrderPaid][充值订单({}) tradeNo({})不匹配，请进行处理！payOrder 数据是：{}]",
                    recharge.getTradeNo(), tradeNo, toJsonString(payOrder));
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
        }
        return payOrder;
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PayWalletRechargeServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
